﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Media.Imaging;
using ArgusLight;
using ArgusLight.Drawing;

namespace Fractals
{
	enum ZoomState : byte
	{
		None,
		CenterIn,
		CenterOut,
		EdgeIn,
		EdgeOut,
		Rectangle
	}

	public enum ZoomMode : byte
	{
		Center,
		Edge,
		Center2,
		Edge2
	}

	public partial class FractalImage : UserControl
	{
		public struct SettingsStr
		{
			public int CrossLineWidth;
			public int CrossRadius;
			public int RectLineWidth;
			public ZoomMode ZoomMode;
			public double ZoomPerMouseScroll;
			public bool KeepRatio;

			public static SettingsStr Standard
			{
				get
				{
					SettingsStr RetVal = new SettingsStr();
					RetVal.CrossLineWidth = 1;
					RetVal.CrossRadius = 8;
					RetVal.RectLineWidth = 1;
					RetVal.ZoomMode = ZoomMode.Center2;
					RetVal.ZoomPerMouseScroll = 1.1;
					RetVal.KeepRatio = true;
					return RetVal;
				}
			}
		}

		public delegate void ZoomRequestHandler(Rect ZoomArea);

		public event ZoomRequestHandler ZoomRequest;
		public event RoutedPropertyChangedEventHandler<Rect> PreviewRectChanged;

		private BitmapSource image;
		private ZoomState zoomState;
		private Point pos;
		private SettingsStr settings;

		public FractalImage()
		{
			InitializeComponent();
			//this.PreviewRect = Rect.Empty;
			this.Settings = SettingsStr.Standard;
		}

		private void UserControl_Loaded_1(object sender, RoutedEventArgs e)
		{
			App.Current.RootVisual.KeyDown += RootVisual_KeyDown;
		}

		private void UserControl_SizeChanged_1(object sender, SizeChangedEventArgs e)
		{
			this.canvas.Width = this.Width;
			this.canvas.Height = this.Height;
			this.cImage.Width = this.Width;
			this.cImage.Height = this.Height;
			this.cImageCross.Width = this.Width;
			this.cImageCross.Height = this.Height;
			this.cImageRectangle.Width = this.Width;
			this.cImageRectangle.Height = this.Height;
			this.canvas.Clip = new RectangleGeometry() { Rect = new Rect(0, 0, this.canvas.Width, this.canvas.Height) };
		}

		public BitmapSource Source
		{ 
			get { return this.image; }
			set
			{
				this.image = value;
				this.cImage.Source = value;
			}
		}

		public SettingsStr Settings
		{
			get { return this.settings; }
			set
			{
				this.settings = value;
			}
		}

		private Rect previewRect = Rect.Empty;
		public Rect PreviewRect
		{
			get
			{
				return this.previewRect;
			}
			set
			{
				if (value != this.previewRect)
				{
					if (this.PreviewRectChanged != null)
					{
						this.PreviewRectChanged(this, new RoutedPropertyChangedEventArgs<Rect>(this.previewRect, value));
					}
					this.previewRect = value;
				}
			}
		}

		private void OnZoomRequest(Rect ZoomArea)
		{
			this.zoomState = ZoomState.None;
			this.RectSelection.Visibility = System.Windows.Visibility.Collapsed;
			if (this.ZoomRequest != null)
				this.ZoomRequest(ZoomArea);
			this.PreviewRect = Rect.Empty;
		}

		private void RootVisual_KeyDown(object sender, KeyEventArgs e)
		{
			if (e.Key == Key.Enter)
			{
				if (this.zoomState == ZoomState.Rectangle)
				{
					this.OnZoomRequest(this.RectSelection.BoundaryRect);
				}
			}
			else if (e.Key == Key.Escape)
			{
				this.CancelZoom();
			}
		}

		#region MouseAndZooming
		protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
		{
			Point location = e.GetPosition(this.cImage);

			if (this.zoomState == ZoomState.None)
			{
				if (this.settings.ZoomMode == ZoomMode.Edge || this.settings.ZoomMode == ZoomMode.Edge2)
				{
					this.ZoomAction_StartEdgeIn(location);
				}
				else if (this.settings.ZoomMode == ZoomMode.Center || this.settings.ZoomMode == ZoomMode.Center2)
				{
					this.ZoomAction_StartCenterIn(location);
				}
			}
			else
			{
				if (location == this.pos)
				{
					this.zoomState = ZoomState.None;
				}
				else if (this.zoomState == ZoomState.CenterIn)
				{
					this.ZoomAction_ZoomCenterIn(location);
				}
				else if (this.zoomState == ZoomState.EdgeIn)
				{
					this.ZoomAction_ZoomEdgeIn(location);
				}
				else if (this.zoomState == ZoomState.CenterOut)
				{
					this.ZoomAction_ZoomCenterOut(location);
				}
				else if (this.zoomState == ZoomState.EdgeOut)
				{
					this.ZoomAction_ZoomEdgeOut(location);
				}
				this.cImageCross.Source = null;
				this.cImageRectangle.Source = null;
			}
			base.OnMouseLeftButtonDown(e);
		}

		protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
		{
			e.Handled = true;
			Point location = e.GetPosition(this.cImage);

			if (this.zoomState == ZoomState.None)
			{
				if (this.settings.ZoomMode == ZoomMode.Edge || this.settings.ZoomMode == ZoomMode.Edge2)
				{
					this.ZoomAction_StartEdgeOut(location);
				}
				else if (this.settings.ZoomMode == ZoomMode.Center || this.settings.ZoomMode == ZoomMode.Center2)
				{
					this.ZoomAction_StartCenterOut(location);
				}
			}
			else
			{
				this.CancelZoom();
			}
			base.OnMouseRightButtonDown(e);
		}

		private void CancelZoom()
		{
			this.zoomState = ZoomState.None;
			this.cImageCross.Source = null;
			this.cImageRectangle.Source = null;
			this.RectSelection.Visibility = System.Windows.Visibility.Collapsed;
			this.PreviewRect = Rect.Empty;
		}

		protected override void OnMouseWheel(MouseWheelEventArgs e)
		{
			base.OnMouseWheel(e);

			if (this.zoomState != ZoomState.Rectangle)
				return;

			Rect rect = this.RectSelection.BoundaryRect;
			Point center = rect.GetCenter();
			Size size = rect.GetSize();
			if (e.Delta == 0)
			{
				return;
			}
			else if (e.Delta > 0)
			{
				size.Width *= this.settings.ZoomPerMouseScroll;
				size.Height *= this.settings.ZoomPerMouseScroll;
			}
			else
			{
				size.Width *= 1/this.settings.ZoomPerMouseScroll;
				size.Height *= 1/this.settings.ZoomPerMouseScroll;
			}
			rect = size.ToRectangle(center, System.Windows.ExtensionMethods.RectOption.Center);
			this.RectSelection.BoundaryRect = rect;
			this.PreviewRect = rect;
			e.Handled = true;
		}

		protected override void OnMouseMove(MouseEventArgs e)
		{
			if (this.zoomState == ZoomState.None)
				return;

			Point location = e.GetPosition(this.cImage);

			if (this.zoomState == ZoomState.CenterIn || this.zoomState == ZoomState.CenterOut)
			{
				this.ZoomAction_MoveCenter(location);
			}
			else if (this.zoomState == ZoomState.EdgeIn || this.zoomState == ZoomState.EdgeOut)
			{
				this.ZoomAction_MoveEdge(location);
			}
			else if (this.zoomState == ZoomState.Rectangle)
			{
				this.PreviewRect = this.RectSelection.BoundaryRect;
			}
			base.OnMouseMove(e);
		}

		private void ZoomAction_StartEdgeIn(Point location)
		{
			this.zoomState = ZoomState.EdgeIn;
			this.pos = location;
		}

		private void ZoomAction_StartCenterIn(Point location)
		{
			this.zoomState = ZoomState.CenterIn;
			this.pos = location;
			this.DrawCross(location);
		}

		private void ZoomAction_ZoomCenterIn(Point location)
		{
			Size size = new Size(2 * Math.Abs(this.pos.X - location.X), 2 * Math.Abs(this.pos.Y - location.Y));
			if (size.Width == 0 || size.Height == 0)
				return;
			if (this.settings.KeepRatio == true)
			{
				size = size.ChangeRatio(this.Width / this.Height, System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
			}
			this.PreviewRect = size.ToRectangle(this.pos, System.Windows.ExtensionMethods.RectOption.Center);

			if ((this.settings.ZoomMode == ZoomMode.Center && Keyboard.Modifiers != ModifierKeys.Control) ||
				(this.settings.ZoomMode == ZoomMode.Center2 && Keyboard.Modifiers == ModifierKeys.Control))
			{
				this.OnZoomRequest(this.PreviewRect);
				this.zoomState = ZoomState.None;
			}
			else if ((this.settings.ZoomMode == ZoomMode.Center2 && Keyboard.Modifiers != ModifierKeys.Control) ||
					(this.settings.ZoomMode == ZoomMode.Center && Keyboard.Modifiers == ModifierKeys.Control))
			{
				this.RectSelection.BoundaryRect = this.PreviewRect;
				this.RectSelection.Visibility = System.Windows.Visibility.Visible;
				this.zoomState = ZoomState.Rectangle;
			}
			else
			{
				this.zoomState = ZoomState.None;
			}
		}

		private void ZoomAction_ZoomEdgeIn(Point location)
		{
			Rect rect = new Rect(this.pos, location);
			if (rect.Width == 0 || rect.Height == 0)
				return;
			if (this.settings.KeepRatio == true)
			{
				System.Windows.ExtensionMethods.RectOption opt = rect.GetRectOption(this.pos);
				rect = rect.ChangeRatio(this.Width / this.Height, System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize, opt);
			}

			this.PreviewRect = rect;

			if ((this.settings.ZoomMode == ZoomMode.Edge && Keyboard.Modifiers != ModifierKeys.Control) ||
				(this.settings.ZoomMode == ZoomMode.Edge2 && Keyboard.Modifiers == ModifierKeys.Control))
			{
				this.OnZoomRequest(rect);
				this.zoomState = ZoomState.None;
			}
			else if ((this.settings.ZoomMode == ZoomMode.Edge2 && Keyboard.Modifiers != ModifierKeys.Control) ||
					(this.settings.ZoomMode == ZoomMode.Edge && Keyboard.Modifiers == ModifierKeys.Control))
			{
				this.RectSelection.BoundaryRect = rect;
				this.RectSelection.Visibility = System.Windows.Visibility.Visible;
				this.zoomState = ZoomState.Rectangle;
			}
			else
			{
				this.zoomState = ZoomState.None;
			}
		}

		private void ZoomAction_ZoomCenterOut(Point location)
		{
			Size size = new Size(2 * Math.Abs(this.pos.X - location.X), 2 * Math.Abs(this.pos.Y - location.Y));
			if (size.Width == 0 || size.Height == 0)
				return;
			if (this.settings.KeepRatio == true)
			{
				size = size.ChangeRatio(this.Width / this.Height, System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
			}
			size.Width = this.Width * this.Width / size.Width;
			size.Height = this.Height * this.Height / size.Height;
			this.PreviewRect = size.ToRectangle(this.pos, System.Windows.ExtensionMethods.RectOption.Center);
			this.OnZoomRequest(this.PreviewRect);
			this.zoomState = ZoomState.None;
		}

		private void ZoomAction_ZoomEdgeOut(Point location)
		{
			Rect rect = new Rect(this.pos, location);
			Size size = rect.GetSize();
			if (size.Width == 0 || size.Height == 0)
				return;
			if (this.settings.KeepRatio == true)
			{
				size = size.ChangeRatio(this.Width / this.Height, System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
			}
			size.Width = this.Width * this.Width / size.Width;
			size.Height = this.Height * this.Height / size.Height;

			System.Windows.ExtensionMethods.RectOption opt = rect.GetRectOption(this.pos);
			this.PreviewRect = size.ToRectangle(this.pos, opt);
			this.OnZoomRequest(this.PreviewRect);
			this.zoomState = ZoomState.None;
		}

		private void ZoomAction_StartEdgeOut(Point location)
		{
			this.zoomState = ZoomState.EdgeOut;
			this.pos = location;
		}

		private void ZoomAction_StartCenterOut(Point location)
		{
			this.zoomState = ZoomState.CenterOut;
			this.pos = location;
			this.DrawCross(location);
		}

		private void ZoomAction_MoveCenter(Point location)
		{
			Size size = new Size(2 * Math.Abs(this.pos.X - location.X), 2 * Math.Abs(this.pos.Y - location.Y));
			if (size.Width == 0 || size.Height == 0)
				return;
			if (this.settings.KeepRatio == true)
			{
				size = size.ChangeRatio(this.Width / this.Height, System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize);
			}
			this.PreviewRect = size.ToRectangle(this.pos, System.Windows.ExtensionMethods.RectOption.Center);
			this.DrawRect();
		}

		private void ZoomAction_MoveEdge(Point location)
		{
			Rect r = new Rect(this.pos, location);
			if (r.Width == 0 || r.Height == 0)
				return;
			if (this.settings.KeepRatio == true)
			{
				System.Windows.ExtensionMethods.RectOption opt = r.GetRectOption(this.pos);
				r = r.ChangeRatio(this.Width / this.Height, System.Windows.ExtensionMethods.ChangeRatioOptions.IncreaseAreaSize, opt);
			}
			this.PreviewRect = r;
			this.DrawRect();
		}
		#endregion

		#region Drawing
		private void DrawCross(Point Pos)
		{
			//Bitmap BK = new Bitmap(Background);
			int Radius = this.settings.CrossRadius;
			float LineWidth = this.settings.CrossLineWidth/2.0f;

			Bitmap RetVal = new Bitmap((int)this.Width, (int)this.Height);
			Bitmap source = new Bitmap(this.image);

			Rect Boundaries = new Rect(0, 0, RetVal.Width - 1, RetVal.Height - 1);

			for (Int32 x = (int)(Pos.X - Radius); x < Pos.X + Radius+1; x++)
			{
				for (Int32 y = (int)(Pos.Y - LineWidth)+1; y < (int)(Pos.Y + LineWidth)+1; y++)
				{
					if (Boundaries.Contains(new Point(x, y)) == true)
					{
						Color c = source.GetPixel(x, y);
						RetVal.SetPixel(x, y, c.Invert());
					}
				}
			}

			for (Int32 y = (int)(Pos.Y - Radius); y < Pos.Y + Radius+1; y++)
			{
				for (Int32 x = (int)(Pos.X - LineWidth)+1; x < (int)(Pos.X + LineWidth)+1; x++)
				{
					if (Boundaries.Contains(new Point(x, y)) == true)
					{
						Color c = source.GetPixel(x, y);
						RetVal.SetPixel(x, y, c.Invert());
					}
				}
			}
			RetVal.Invalidate();
			this.cImageCross.Source = RetVal.WritableBitmap;
		}

		private void DrawRect()
		{
			Rect rectangle = this.PreviewRect;
			float LineWidth = this.settings.RectLineWidth / 2.0f;

			Bitmap bitmap = new Bitmap((int)this.Width, (int)this.Height);
			Bitmap source = new Bitmap(this.image);
			Rect Boundaries = new Rect(0, 0, bitmap.Width-1, bitmap.Height-1);

			for (int x = (int)(rectangle.X + LineWidth)+1; x < (int)(rectangle.Right - LineWidth)+1; x++)
			{
				for (int y = (int)(rectangle.Y - LineWidth) + 1; y < (int)(rectangle.Y + LineWidth) + 1; y++)
				{
					if (Boundaries.Contains(new Point(x, y)) == true)
					{
						Color c = source.GetPixel(x, y);
						bitmap.SetPixel(x, y, c.Invert());
					}
				}

				for (int y = (int)(rectangle.Bottom - LineWidth) + 1; y < (int)(rectangle.Bottom + LineWidth) + 1; y++)
				{
					if (Boundaries.Contains(new Point(x, y)) == true)
					{
						Color c = source.GetPixel(x, y);
						bitmap.SetPixel(x, y, c.Invert());
					}
				}
			}

			for (int y = (int)(rectangle.Y - LineWidth) + 1; y < (int)(rectangle.Bottom + LineWidth) + 1; y++)
			{
				for (int x = (int)(rectangle.X - LineWidth) + 1; x < (int)(rectangle.X + LineWidth) + 1; x++)
				{
					if (Boundaries.Contains(new Point(x, y)) == true)
					{
						Color c = source.GetPixel(x, y);
						bitmap.SetPixel(x, y, c.Invert());
					}
				}

				for (int x = (int)(rectangle.Right - LineWidth) + 1; x < (int)(rectangle.Right + LineWidth) + 1; x++)
				{
					if (Boundaries.Contains(new Point(x, y)) == true)
					{
						Color c = source.GetPixel(x, y);
						bitmap.SetPixel(x, y, c.Invert());
					}
				}
			}

			this.cImageRectangle.Source = bitmap.WritableBitmap;
		}
#endregion

		private void DoubleClickLayer_MouseDoubleLeftClick_1(object sender, MouseButtonEventArgs e)
		{
			this.OnZoomRequest(this.RectSelection.BoundaryRect);
		}
	}
}
